/*
  接口返回的数据中存在某些字段和前端View层的字段对应不一致的情况
  此时可以通过 propertyMap 装饰器搭配 ModelMapper 来处理这种情况

  例子：

  1. 模型类中声明字段映射关系
  ```ts
  // 表示服务端返回的字段是title，但是前端 service 暴露给组件的字段是 name。
  @propertyMap('title')
  public name: string;
  ```

  2. 数据服务中如下处理
  ```ts
  return this.http.get<TodoModel[]>(this.url).pipe(
    map(data =>
      data.map((item: any) => {
        return new ModelMapper(TodoModel).map(item);
      })
    )
  );
  ```
*/
// 字段映射关系的修饰器
export function propertyMap(sourceProperty: string) {
  return function(target: any, propertyKey: string) {
    if (!target.constructor._propertyMap) {
      target.constructor._propertyMap = {}
    }
    target.constructor._propertyMap[propertyKey] = sourceProperty
  }
}

// 字段映射服务类
export class ModelMapper<T> {
  _propertyMapping: any
  _target: any
  constructor(type: { new (): T }) {
    this._target = new type()
    this._propertyMapping = this._target.constructor._propertyMap
    Object.keys(this._propertyMapping).map((key: string) => (this._target[key] = null))
  }

  // 将修饰器 propertyMap 的入参称为 原字段
  // 该方法将原字段映射成新字段
  // 一般取数据的时候用该方法
  map(source): T {
    Object.keys(this._target).forEach(key => {
      const mappedKey = this._propertyMapping[key]
      if (mappedKey) {
        this._target[key] = source[mappedKey]
      } else {
        this._target[key] = source[key]
      }
    })
    Object.keys(source).forEach(key => {
      const targetKeys = Object.keys(this._target)
      if (targetKeys.indexOf(key) === -1) {
        this._target[key] = source[key]
      }
    })
    return this._target
  }

  // 将修饰器 propertyMap 的入参称为 原字段
  // 该方法将字段映射成原字段
  // 一般保存数据的时候用该方法
  demap(source): any {
    const result = {}
    Object.keys(source).forEach(key => {
      const mappedKey = this._propertyMapping[key]
      if (mappedKey) {
        result[mappedKey] = source[key]
      } else {
        result[key] = source[key]
      }
    })

    return result
  }
}
